package fons.model.ontology;

import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public final class Value {
	private SimpleMeasurement measurement;
	private final Map<Dimension, Role> dimensions = Maps.newHashMap();
	
	private final double value;
	private final Context context;
	
	public final static Comparator<Value> COMPARE_VALUE_ONLY = new Comparator<Value>() {
		@Override
		public int compare(Value v1, Value v2) {
			if (v1 == Context.missingValue) return -1;
			if (v2 == Context.missingValue) return 1;
			
			return v1.getValue() > v2.getValue() ? 1
				 : v2.getValue() > v1.getValue() ? -1
				 : compareByDimensions(v1, v2);
		}
		
		private int compareByDimensions(Value v1, Value v2) {
			if (v1.getDifferentDimensions(v2).size() == 0) return 0;
			return 1;
		}
		
	};
	
	public final static Comparator<Value> COMPARE_SAFELY = new Comparator<Value>() {
		@Override
		public int compare(Value v1, Value v2) {
			if (v1.getMeasurement().equals(v2.getMeasurement())) {
				return COMPARE_VALUE_ONLY.compare(v1, v2);
			} else {
				return v1.getMeasurement().toString().compareTo(v2.getMeasurement().toString());
			}
		}
	};
	
	public Value as (final Dimension dimension) {
		return this.as(dimension, Context.anyRole);
	}
	
	public Value as (final Dimension dimension, final Role role) {
		this.dimensions.put(dimension, role);
		dimension.addValue(this);
		resetHash();
		return this;
	}
	
	public Value as (final String dimensionName) {
		return this.as(context.getDimension(dimensionName));
	}
	
	public Value as (final String dimensionName, final String roleName) {
		return this.as(context.getDimension(dimensionName), context.getRole(roleName));
	}
	
	public Value in (final String measurementName) {
		this.measurement = context.getMeasurement(measurementName);
		return this;
	}
	
	public Value in (final SimpleMeasurement measurement) {
		this.measurement = measurement;
		return this;
	}
	
	public double getValue () {
		return value;
	}
	
	public SimpleMeasurement getMeasurement() {
		return this.measurement;
	}
	
	private Set<Dimension> tmpDimensions;
	public Set<Dimension> getDimensions() {
		if (tmpDimensions == null || tmpDimensions.size() != dimensions.size()) {
			return tmpDimensions = dimensions.keySet();
		} else {
			return tmpDimensions;
		}
		//return Collections.unmodifiableSet(dimensions);
	}

	Value (final double value, final Context context) {
		this.value = value;
		this.context = context;
	}
	
	/*Integer hash = null;
	*/
	private void resetHash() {
		//hash = null;
	}
/*
	@Override
	public int hashCode() {
		if (hash != null) return hash;
		
		final int prime = 31;
		int result = 1;
		result = prime * result + ((context == null) ? 0 : context.hashCode());
		result = prime * result
				+ ((dimensions == null) ? 0 : dimensions.hashCode());
		hash = new Integer(result);
		return result;
	}*/

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Value other = (Value) obj;
		if (context == null) {
			if (other.context != null)
				return false;
		} else if (!context.equals(other.context))
			return false;
		if (dimensions == null) {
			if (other.dimensions != null)
				return false;
		} else if (!dimensions.equals(other.dimensions))
			return false;
		return true;
	}

	public Dimension childOfAny (Set<Dimension> aggregateDimensions) {
		for (Dimension d: getDimensions()) {
			for (Dimension p: aggregateDimensions) {
				if (d.isDirectChildOf(p)) return p;
			}
		}
		return null;
	}
	
	public Dimension partOfAny (Set<Dimension> aggregateDimensions) {
		for (Dimension d: getDimensions()) {
			for (Dimension p: aggregateDimensions) {
				if (d.isDirectPartOf(p)) return p;
			}
		}
		return null;
	}
	
	public Dimension hasAnyOf (Set<? extends Dimension> dimensions) {
		if (dimensions == null) return null;
		
		for (Dimension d: this.getDimensions()) {
			for (Dimension e: dimensions) {
				if (/*d.equals(e)*/d == e) return e;
			}
		}
		return null;
	}

	@Override
	public String toString() {
		return "Value\n[measurement=" + getMeasurement() + ",\ndimensions="
				+ dimensions + ",\nvalue=" + value + "]";
	}
	
	public boolean is(Dimension dimension) {
		for (Dimension d: getDimensions()) {
			if (d.equals(dimension)) return true;
		}
		return false;
	}

	public boolean isDirectChildOf(Dimension parent) {
		for (Dimension d: getDimensions()) {
			if (d.isDirectChildOf(parent)) return true;
		}
		return false;
	}

	public boolean isDirectPartOf(Dimension ensemble) {
		for (Dimension d: getDimensions()) {
			if (d.isDirectPartOf(ensemble)) return true;
		}
		return false;
	}

	public boolean isDeepChildOf(Dimension parent) {
		for (Dimension d: getDimensions()) {
			if (d.isDeepChildOf(parent)) return true;
		}
		return false;
	}

	public boolean isDeepPartOf(Dimension ensemble) {
		for (Dimension d: getDimensions()) {
			if (d.isDeepPartOf(ensemble)) return true;
		}
		return false;
	}
	
	public Value getPredecessorIn (Dimension parent) {
		if (parent.isFirst()) return null;
		
		final Match matcher = new Match();
		for (Dimension d: getDimensions()) {
			if (d.isDirectChildOf(parent)) {
				matcher.add(d.getPredecessor());
			} else {
				matcher.add(d);
			}
		}
		
		for (Value each: context.getValues()) {
			if (matcher.matches(each)) return each;
		}
		
		return null;
	}
	
	public Value getSuccessorIn (Dimension parent) {
		if (parent.isLast()) return null;
		
		final Match matcher = new Match();
		for (Dimension d: getDimensions()) {
			if (d.isDirectChildOf(parent)) {
				matcher.add(d.getSuccessor());
			} else {
				matcher.add(d);
			}
		}
		
		for (Value each: context.getValues()) {
			if (matcher.matches(each)) return each;
		}
		
		return null;
	}
	
	public Set<Dimension> getSameDimensions (final Value other) {
		return Sets.intersection(this.getDimensions(), other.getDimensions());
	}
	
	public Set<Dimension> getDifferentDimensions (final Value other) {
		return Sets.union(Sets.difference(getDimensions(), other.getDimensions()), 
						  Sets.difference(other.getDimensions(), getDimensions()));
	}

	public static Collection<Value> getSmallestValueSet(Set<Dimension> tmp) {
		Dimension least = null;
		for (Dimension dim: tmp) {
			if (least == null) least = dim;
			else {
				if (dim.getValues().size() < least.getValues().size()) {
					least = dim;
				}
			}
		}
		if (least != null) {
			return least.getValues();
		} 
		
		return Lists.newArrayList();
	}

	public static boolean isValid(Value value) {
		return value != Context.missingValue 
			&& (!Double.isNaN(value.getValue()))
			&& (!Double.isInfinite(value.getValue()));				
	}

	public boolean hasAllOf(Collection<Dimension> dimensions) {
		for (Dimension d: dimensions) {
			if (!this.is(d)) return false;
		}
		return true;
	}
	
	public boolean hasAllOf(Dimension[] dimensions) {
		for (Dimension d: dimensions) {
			if (!this.is(d)) return false;
		}
		return true;
	}
	
	public Set<Dimension> getDimensionsFor (Role role) {
		if (role == Context.everyRole) return getDimensions();
		
		Set<Dimension> tmp = Sets.newHashSet();
		for (Entry<Dimension, Role> entry: dimensions.entrySet()) {
			if (entry.getValue().equals(role)) {
				tmp.add(entry.getKey());
			}
		}
		return tmp;
	}
	
}
